﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Input;

#if WINDOWS_UWP || NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
#else
using System.Windows;
using System.Windows.Controls;
#endif

using Outcoder.Reflection;
using Outcoder.UI.Xaml;

namespace Outcoder.CommandModel
{
	public class CommandAttachment
	{
		ICommand command;
		object commandParameter;
		WeakReference targetReference;
		readonly EventHandler canExecuteChangedHandler;
		Action removeHandlerAction;
		//bool attached;

		public CommandAttachment(DependencyObject targetElement)
		{
			canExecuteChangedHandler = HandleCanExecuteChangedHandler;

			Attach(targetElement);
		}

		void HandleCanExecuteChangedHandler(object o, EventArgs args)
		{
			UpdateEnabledState();
		}

		public void Detach()
		{
			if (removeHandlerAction != null)
			{
				removeHandlerAction();
				removeHandlerAction = null;
			}

			Command = null;
			EventName = null;
			CommandParameter = null;
			targetReference.Target = null;

			//attached = false;
		}

		public void Attach(DependencyObject targetElement)
		{
			if (targetReference == null)
			{
				targetReference = new WeakReference(targetElement);
			}
			else
			{
				targetReference.Target = targetElement;
			}
		}

		public void HandleExecuteCommand(object sender, EventArgs args)
		{
			ExecuteCommand();
		}

#if WINDOWS_PHONE
		public void HandleGestureExecuteCommand(object sender, GestureEventArgs args)
		{
			ExecuteCommand();
		}
#endif

		protected virtual void ExecuteCommand()
		{
			if (Command != null)
			{
				Command.Execute(CommandParameter);
			}
		}

		public ICommand Command
		{
			get
			{
				return command;
			}
			set
			{
				if (command == value)
				{
					return;
				}

				if (command != null)
				{
					command.CanExecuteChanged -= canExecuteChangedHandler;
				}

				command = value;
				if (command != null)
				{
					command.CanExecuteChanged += canExecuteChangedHandler;
					UpdateEnabledState();
				}
			}
		}

		public object CommandParameter
		{
			get
			{
				return commandParameter;
			}
			set
			{
				if (commandParameter != value)
				{
					commandParameter = value;
					UpdateEnabledState();
				}
			}
		}

		string eventName;

		public string EventName
		{
			get
			{
				return eventName;
			}
			set
			{
				if (eventName != value)
				{
					eventName = value;
					if (string.IsNullOrEmpty(eventName))
					{
						return;
					}
					DependencyObject element = TargetElement;
					if (element == null)
					{
						return;
					}

#if NETFX_CORE
					EventInfo eventInfo = element.GetType().GetRuntimeEvent(eventName);
#else
					EventInfo eventInfo = element.GetType().GetEvent(eventName);
#endif
					if (eventInfo == null)
					{
						throw new ArgumentException(string.Format(
							"Event name '{0}' not found on FrameworkElement '{1}'",
							eventName, element));
					}

					if (EnvironmentValues.DesignTime)
					{
						return;
					}

					Delegate handler;
					//if (!handlerCache.TryGetValue(eventInfo.EventHandlerType, out handler))
					//{
#if NETFX_CORE
						if (typeof(EventHandler<EventArgs>).GetTypeInfo().IsAssignableFrom(eventInfo.EventHandlerType.GetTypeInfo()))
						{
							MethodInfo methodInfo = GetType().GetTypeInfo().GetDeclaredMethod("HandleExecuteCommand");
							handler = methodInfo.CreateDelegate(
								eventInfo.EventHandlerType, this/*, "HandleExecuteCommand"*/);
							eventInfo.AddEventHandler(element, handler);
							removeHandlerAction = () => eventInfo.RemoveEventHandler(element, handler);
						}
#else
					if (typeof(EventHandler<EventArgs>).IsAssignableFrom(eventInfo.EventHandlerType))
					{
						/* This is faster for non-generic event handlers, 
							 * and relies on event covarience. */
						handler = Delegate.CreateDelegate(
							eventInfo.EventHandlerType, this, "HandleExecuteCommand");
						eventInfo.AddEventHandler(element, handler);
						removeHandlerAction = () => eventInfo.RemoveEventHandler(element, handler);
					}
#endif

#if WINDOWS_PHONE
					else if (typeof(EventHandler<GestureEventArgs>).IsAssignableFrom(eventInfo.EventHandlerType))
					{
						/* This is faster for non-generic event handlers, 
							 * and relies on event covarience. */
						handler = Delegate.CreateDelegate(
							eventInfo.EventHandlerType, this, "HandleGestureExecuteCommand");
						eventInfo.AddEventHandler(element, handler);
						removeHandlerAction = () => eventInfo.RemoveEventHandler(element, handler);
					}
#endif
					else
					{
						//#if WINDOWS_PHONE
						//							throw new NotSupportedException(
						//								"Unable to create handler for generic event arguments in Windows Phone.");
						//#else
						handler = eventInfo.CreateHandler(ExecuteCommand);
						eventInfo.AddEventHandler(element, handler);
						removeHandlerAction = () => eventInfo.RemoveEventHandler(element, handler);
						//#endif
					}
					handlerCache[eventInfo.EventHandlerType] = handler;
					//}
				}
			}
		}

		static readonly Dictionary<Type, Delegate> handlerCache = new Dictionary<Type, Delegate>();

		protected DependencyObject TargetElement
		{
			get
			{
				return (DependencyObject)targetReference.Target;
			}
		}

		protected virtual void UpdateEnabledState()
		{
			if (TargetElement == null)
			{
				Command = null;
				CommandParameter = null;
			}
			else if (Command != null)
			{
				var targetControl = TargetElement as Control;
				if (targetControl != null)
				{
					targetControl.IsEnabled = Command.CanExecute(CommandParameter);
				}
			}
		}
	}
}